package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.dto.SignInMessage;
import com.tianji.learning.dto.SignResultVO;
import com.tianji.learning.entity.PointsBoardSeason;
import com.tianji.learning.entity.PointsRecord;
import com.tianji.learning.enums.PointsRecordType;
import com.tianji.learning.enums.RedisKey;
import com.tianji.learning.mapper.PointsBoardSeasonMapper;
import com.tianji.learning.mapper.PointsRecordMapper;
import com.tianji.learning.service.IPointsBoardSeasonService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.service.IPointsRecordService;
import com.tianji.learning.vo.PointsBoardSeasonVO;
import io.lettuce.core.BitFieldArgs;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author author
 * @since 2023-06-01
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class PointsBoardSeasonServiceImpl extends ServiceImpl<PointsBoardSeasonMapper, PointsBoardSeason> implements IPointsBoardSeasonService {

    private final StringRedisTemplate redisTemplate;
    private final PointsRecordMapper recordMapper;
    private final RabbitMqHelper mqHelper;
    /**
     * 签到功能接口
     *
     * @return
     */
    @Override
    public SignResultVO addSignRecords() {
        Long userId = UserContext.getUser();
       // 1.获取当前月天数 day
        LocalDate now = LocalDate.now();
        String key = RedisKey.SIGN_RECORD_KEY_PREFIX
                + userId
                + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        int offset = now.getDayOfMonth() -1;
       // 2.使用redis的BitMap.set(0,day) 1？已打卡：未打卡
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if (exists == null || exists) {
            // 3.1已打卡：重复打卡，直接return
            throw new BadRequestException("重复签到！");
        }
       // 3.2未打卡：打卡成功
        int signDays = countSignDays(key, now.getDayOfMonth());
        int season = 0;
       // 4.计算积分，
       // 5.7/14/28天连续打卡对应不同的分数
        switch (signDays){
            case 7:
                season += 10;
                break;
            case 14:
                season += 100;
                break;
            case 28:
                season += 1000;
                break;
            default:
                break;
        }
       // 6.将积分作为mq消息发送
        mqHelper.send(
                MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId, season + 1));// 签到积分是基本得分+奖励积分
        // 5.封装返回
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(season);
        return vo;
    }

    /**
     * 监听签到事件
     *
     * @param userId
     * @param points
     * @param type
     */
    @Override
    public void addPointsRecord(Long userId, Integer points, PointsRecordType type) {
        //1.判断积分有无上线 1?判断今日有无上限：直接保存积分表
        LocalDateTime now = LocalDateTime.now();
        int maxPoints = type.getMaxPoints();
        int realPoints = points;
        if (maxPoints > 0) {
            //2.判断今日有无上限
            LocalDateTime begin = DateUtils.getDayStartTime(now);
            LocalDateTime end = DateUtils.getDayEndTime(now);
            // 2.1.查询今日已得积分
            Integer currentPoints = this.queryUserPointsByTypeAndDate(userId, type, begin, end);
            // 2.2.判断是否超过上限
            if ( currentPoints == null || currentPoints >= maxPoints) {
                // 2.3.超过，直接结束
                return;
            }
            // 2.4.没超过，保存积分记录
            if (currentPoints + points > maxPoints) {
                realPoints = maxPoints - currentPoints;
            }
        }
            // 3.没有，直接保存积分记录
            PointsRecord p = new PointsRecord();
            p.setPoints(realPoints);
            p.setUserId(userId);
            p.setType(type);
            recordMapper.insert(p);

        // 4.更新总积分到Redis
        String key = RedisKey.POINTS_BOARD_KEY_PREFIX + now.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);
        redisTemplate.opsForZSet().incrementScore(key, userId.toString(), realPoints);
    }

    /**
     * 本月签到情况
     *
     * @return
     */
    @Override
    public int[] getSignRecords() {
        Long userId = UserContext.getUser();
        // 1.获取当前月天数 day
        LocalDate now = LocalDate.now();
        String key = RedisKey.SIGN_RECORD_KEY_PREFIX
                + userId
                + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        int offset = now.getDayOfMonth();
        // 2.使用redis的BitMap.set(0,day) 1？已打卡：未打卡
        List<Long> number = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(offset)).valueAt(0));
        //3.处理数据
        int[] arr = new int[31];
        if (number != null) {
            int num = number.get(0).intValue();
            for (int i = 0; num >= 1; i++) {
                if((num & 1) == 1){
                    arr[i] = 1;
                }else {
                    arr[i] = 0;
                }
                num >>>= 1;
            }
        }

        return arr;
    }

    /**
     * 查询所有赛季
     *
     * @return
     */
    @Override
    public List<PointsBoardSeasonVO> queryMyPointsBoardSeason() {
        List<PointsBoardSeason> list = this.list();
        List<PointsBoardSeasonVO> vos = BeanUtils.copyList(list, PointsBoardSeasonVO.class);
        return vos;
    }

    /**
     * 查询赛季id
     *
     * @param time
     * @return
     */
    @Override
    public Integer querySeasonByTime(LocalDateTime time) {
        Optional<PointsBoardSeason> optional = lambdaQuery()
                .le(PointsBoardSeason::getBeginTime, time)
                .ge(PointsBoardSeason::getEndTime, time)
                .oneOpt();
        return optional.map(PointsBoardSeason::getId).orElse(null);
    }

    private Integer queryUserPointsByTypeAndDate(Long userId, PointsRecordType type, LocalDateTime begin, LocalDateTime end) {
        // 1.查询条件
        QueryWrapper<PointsRecord> wrapper = new QueryWrapper<>();
        wrapper.lambda()
                .eq(PointsRecord::getUserId, userId)
                .eq(type != null, PointsRecord::getType, type)
                .between(begin != null && end != null, PointsRecord::getCreateTime, begin, end);
        return recordMapper.queryUserPointsByTypeAndDate(wrapper);
    }

    private int countSignDays(String key, int dayOfMonth) {
        List<Long> number = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (number != null) {
            int num = number.get(0).intValue();
            //计数器
            int count = 0;

            while ((num & 1) == 1 ){
                count++;
                num >>>= 1;
            }
            return count;
        }
        return 0;
    }
}
